package com.leosam.tvbox.mv;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.URLDecoder;
import java.nio.file.Files;
import java.util.Enumeration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.leosam.tvbox.mv.data.Vod;
import com.leosam.tvbox.mv.data.VodResult;
import com.leosam.tvbox.mv.proxy.M3u8Proxy;
import com.leosam.tvbox.mv.service.MvService;
import com.leosam.tvbox.mv.utils.NumberUtils;
import com.leosam.tvbox.mv.utils.StringUtils;
import com.leosam.tvbox.mv.utils.VertxUtils;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.FileSystemAccess;
import io.vertx.ext.web.handler.StaticHandler;

public class MainVerticle extends AbstractVerticle {
  private static final Logger logger = LoggerFactory.getLogger(MainVerticle.class);

  public static MvService mvService;
  private static int port = 0;

  static {
    CompletableFuture.runAsync(MainVerticle::initIndex)
        .thenAcceptAsync((a) -> {
          if (port > 0) {
            logger.info("索引完成，可以请求数据了, HTTP server port " + port);
          } else {
            logger.info("索引完成，可以请求数据了");
          }
        });
  }

  private static void initIndex() {
    try {
      MvService mvService = new MvService();
      mvService.initIndex();
      MainVerticle.mvService = mvService;
    } catch (Exception e) {
      logger.error("初始化索引失败", e);
    }
  }

  @Override
  public void start(Promise<Void> startPromise) throws Exception {
    HttpServer httpServer = vertx.createHttpServer();

    Router router = Router.router(vertx);
    router.route("/vod").handler(this::searchVod);

    registerConfigRoute(router);

    registerDownloadRoute(router);

    registerStaticRoute(router);

    httpServer.requestHandler(router);

    httpServer.listen(7777, http -> {
      if (http.succeeded()) {
        startPromise.complete();
        logger.info("HTTP server started on port 7777");
        try {
          Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
          while (networkInterfaces.hasMoreElements()) {
            NetworkInterface networkInterface = networkInterfaces.nextElement();
            Enumeration<InetAddress> interfaceAddresses = networkInterface.getInetAddresses();
            while (interfaceAddresses.hasMoreElements()) {
              InetAddress address = interfaceAddresses.nextElement();
              if (!address.isLoopbackAddress() && !address.isLinkLocalAddress()
                  && address.getHostAddress().indexOf(":") == -1) {
                logger.info("you can visit: http://{}:{}/vod/admin", address.getHostAddress(), httpServer.actualPort());
              }
            }
          }
        } catch (Exception e) {
          System.err.println("Failed to retrieve network interfaces and IP addresses: " + e.getMessage());
        }
      } else {
        startPromise.fail(http.cause());
        logger.info("HTTP server fail, on port 7777, 启动失败，更换端口重试", http.cause());
      }
    });
    port = httpServer.actualPort();
  }

  private void registerStaticRoute(Router router) {
    router.route("/vod/static/*").handler(StaticHandler.create("static").setDefaultContentEncoding("UTF-8"));

    router.route("/vod/admin").handler(StaticHandler.create("admin.html").setDefaultContentEncoding("UTF-8"));

    router.route("/vod/media/*")
        .handler(StaticHandler.create(FileSystemAccess.ROOT, MvService.DATA_PATH + MvService.MEDIA_PATH)
            .setEnableRangeSupport(true));

    router.route("/vod/proxy/*").handler(this::proxy);
  }

  private void registerDownloadRoute(Router router) {
    router.route(HttpMethod.POST, "/vod/cache").handler(req -> {
      VertxUtils.update(req);

      req.request().body().onSuccess(b -> {
        mvService.cacheVod(b.toString("UTF-8"));
        req.response().end();
      });
    });

    router.route("/vod/api/reload").handler(req -> {
      VertxUtils.update(req);

      initIndex();
      req.response()
          .putHeader("content-type", "text/html;charset=utf-8")
          .end("重建成功");
    });
  }

  private void registerConfigRoute(Router router) {
    router.route("/vod/config/assets/*")
        .handler(StaticHandler.create(FileSystemAccess.ROOT, MvService.DATA_PATH + MvService.CONFIG_PATH + "/assets")
            .setEnableRangeSupport(true));
    router.route("/vod/config/:file").handler(this::echoConfig);

    router.route("/vod/tvbox.json").handler(v -> v.reroute("/vod/config/tvbox.json"));
  }

  private void proxy(RoutingContext req) {
    String absoluteURI = req.request().uri().substring("/vod/proxy/".length());
    try {
      HttpServerResponse response = req.response();
      String uri = URLDecoder.decode(absoluteURI, "UTF-8");
      logger.info("proxy: {}", uri);
      File file = M3u8Proxy.getFile(uri, 3000);
      if (file != null) {
        response.setStatusCode(200);
        if (absoluteURI.endsWith(".ts")) {
          response.putHeader("Content-Type", "video/mp2t");
        } else if (absoluteURI.endsWith(".m3u8")) {
          response.putHeader("Content-Type", "application/vnd.apple.mpegurl");
        }
        response.sendFile(file.getPath());
      } else {
        logger.warn("proxy failed: {} ", uri);
        response.setStatusCode(404);
        response.end();
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
    } catch (UnsupportedEncodingException e) {
      e.printStackTrace();
    }
  }

  /**
   * 参考 <a href=
   * "https://github.com/FongMi/TV/blob/release/app/src/main/java/com/fongmi/android/tv/model/SiteViewModel.java#L32">...</a>
   * 
   * @param req
   */
  private void searchVod(RoutingContext req) {
    VertxUtils.update(req);

    // 获取参数
    String wd = StringUtils.cleanString(StringUtils.trimToEmpty(VertxUtils.queryParam(req, "wd")));
    String ids = StringUtils.trimToEmpty(VertxUtils.queryParam(req, "ids"));
    // String ac = VertxUtils.queryParam(req, "ac");
    String type = VertxUtils.queryParam(req, "t");
    int max = Math.min(Math.max(NumberUtils.toInt(VertxUtils.queryParam(req, "maxCount"), 200), 10), 1000);
    // int page = Math.max(0, NumberUtils.toInt(VertxUtils.queryParam(req, "pg"),
    // 0));

    // 处理结果
    VodResult vodResult = null;
    try {
      // 搜索
      if (mvService != null) {
        if (!wd.isEmpty()) {
          vodResult = mvService.searchVod(wd, max);
        } else if (!ids.isEmpty()) {
          vodResult = mvService.detailVod(ids.split(","), max);
        } else {
          vodResult = mvService.searchVodHome(type);
        }
      }
    } catch (Exception e) {
      logger.error("searchVod fail, wd={}, ids={}, max={}", wd, ids, max, e);
    }

    // 返回数据
    String jsonString;
    try {
      jsonString = Vod.VIEW_OBJ_MAPPER.writeValueAsString(vodResult);
      logger.info(jsonString);
      req.response()
          .putHeader("content-type", "application/json;charset=utf-8")
          .end(jsonString);
    } catch (JsonProcessingException e) {
      logger.error("search vod failed", e);
      req.response()
          .putHeader("content-type", "application/json;charset=utf-8")
          .end("{}");
    }
  }

  /**
   * 回显tvbox配置
   * 
   * @param req 请求
   */
  private void echoConfig(RoutingContext req) {
    VertxUtils.update(req);
    try {
      File file = new File(MvService.DATA_PATH + MvService.CONFIG_PATH, req.pathParam("file"));
      if (!file.exists()) {
        req.response().setStatusCode(404).end();
        return;
      }
      String json = new String(Files.readAllBytes(file.toPath()), "UTF-8");

      JsonObject jsonObject = new JsonObject(json);
      if (jsonObject.getBoolean("cachable", Boolean.TRUE)) {
        JsonArray list = new JsonArray();

        JsonObject node = new JsonObject();
        node.put("key", "ZCH");
        node.put("name", "👀┃ZCH┃视频");
        node.put("type", 1);
        node.put("api", "http://" + VertxUtils.getHost() + "/vod");
        node.put("searchable", 1);
        node.put("quickSearch", 1);
        node.put("filterable", 1);
        list.add(node);

        list.addAll(jsonObject.getJsonArray("sites"));
        jsonObject.put("sites", list);
      }
      req.response()
          .putHeader("content-type", "application/json;charset=utf-8")
          .end(jsonObject.toString());
    } catch (IOException e) {
      logger.error("echo json failed", e);
      req.response()
          .putHeader("content-type", "text/html;charset=utf-8")
          .end("错误:" + e.getMessage());
    }
  }

  public static void main(String[] args) throws Exception {

    try {
      // 创建一个 TrustManager，用于忽略证书校验
      TrustManager[] trustAllCerts = new TrustManager[] {
          new X509TrustManager() {
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
              return null;
            }

            public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
            }

            public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
            }
          }
      };

      // 创建一个 SSLContext，并设置 TrustManager
      SSLContext sslContext = SSLContext.getInstance("TLS");
      sslContext.init(null, trustAllCerts, new java.security.SecureRandom());

      // 设置默认的 SSLContext
      HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());

    } catch (Exception e) {
    }
    VertxOptions vertxOptions = new VertxOptions();
    vertxOptions.setWorkerPoolSize(100);
    vertxOptions.setBlockedThreadCheckInterval(30);
    vertxOptions.setBlockedThreadCheckIntervalUnit(TimeUnit.SECONDS);
    vertxOptions.setMaxEventLoopExecuteTime(60);
    vertxOptions.setMaxEventLoopExecuteTimeUnit(TimeUnit.SECONDS);
    vertxOptions.setWarningExceptionTime(60);
    vertxOptions.setWarningExceptionTimeUnit(TimeUnit.SECONDS);

    Vertx vertx = Vertx.vertx(vertxOptions);
    vertx.deployVerticle(new MainVerticle());
  }

}
